import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useReactFlow, useStoreApi } from 'reactflow';
import produce from 'immer';
import { useStore, useWorkflowStore } from '../store';
import { CUSTOM_NODE, DSL_EXPORT_CHECK, WORKFLOW_DATA_UPDATE } from '../constants';
import type { Node, WorkflowDataUpdater } from '../types';
import { ControlMode } from '../types';
import { getLayoutByDagre, initialEdges, initialNodes } from '../utils';
import { useNodesReadOnly, useSelectionInteractions, useWorkflowReadOnly } from '../hooks';
import { useEdgesInteractions } from './use-edges-interactions';
import { useNodesInteractions } from './use-nodes-interactions';
import { useNodesSyncDraft } from './use-nodes-sync-draft';
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history';
import { useEventEmitterContextContext } from '@/context/event-emitter';
import { fetchWorkflowDraft } from '@/service/workflow';
import { exportAppConfig } from '@/service/apps';
import { useToastContext } from '@/app/components/base/toast';
import { useStore as useAppStore } from '@/app/components/app/store';

export const useWorkflowInteractions = () => {
  const workflowStore = useWorkflowStore();
  const { handleNodeCancelRunningStatus } = useNodesInteractions();
  const { handleEdgeCancelRunningStatus } = useEdgesInteractions();

  const handleCancelDebugAndPreviewPanel = useCallback(() => {
    workflowStore.setState({
      showDebugAndPreviewPanel: false,
      workflowRunningData: undefined,
    });
    handleNodeCancelRunningStatus();
    handleEdgeCancelRunningStatus();
  }, [workflowStore, handleNodeCancelRunningStatus, handleEdgeCancelRunningStatus]);

  return {
    handleCancelDebugAndPreviewPanel,
  };
};

export const useWorkflowMoveMode = () => {
  const setControlMode = useStore((s) => s.setControlMode);
  const { getNodesReadOnly } = useNodesReadOnly();
  const { handleSelectionCancel } = useSelectionInteractions();

  const handleModePointer = useCallback(() => {
    if (getNodesReadOnly()) return;

    setControlMode(ControlMode.Pointer);
  }, [getNodesReadOnly, setControlMode]);

  const handleModeHand = useCallback(() => {
    if (getNodesReadOnly()) return;

    setControlMode(ControlMode.Hand);
    handleSelectionCancel();
  }, [getNodesReadOnly, setControlMode, handleSelectionCancel]);

  return {
    handleModePointer,
    handleModeHand,
  };
};

export const useWorkflowOrganize = () => {
  const workflowStore = useWorkflowStore();
  const store = useStoreApi();
  const reactflow = useReactFlow();
  const { getNodesReadOnly } = useNodesReadOnly();
  const { saveStateToHistory } = useWorkflowHistory();
  const { handleSyncWorkflowDraft } = useNodesSyncDraft();

  const handleLayout = useCallback(async () => {
    if (getNodesReadOnly()) return;
    workflowStore.setState({ nodeAnimation: true });
    const { getNodes, edges, setNodes } = store.getState();
    const { setViewport } = reactflow;
    const nodes = getNodes();
    const layout = getLayoutByDagre(nodes, edges);
    const rankMap = {} as Record<string, Node>;

    nodes.forEach((node) => {
      if (!node.parentId && node.type === CUSTOM_NODE) {
        const rank = layout.node(node.id).rank!;

        if (!rankMap[rank]) {
          rankMap[rank] = node;
        } else {
          if (rankMap[rank].position.y > node.position.y) rankMap[rank] = node;
        }
      }
    });

    const newNodes = produce(nodes, (draft) => {
      draft.forEach((node) => {
        if (!node.parentId && node.type === CUSTOM_NODE) {
          const nodeWithPosition = layout.node(node.id);

          node.position = {
            x: nodeWithPosition.x - node.width! / 2,
            y: nodeWithPosition.y - node.height! / 2 + rankMap[nodeWithPosition.rank!].height! / 2,
          };
        }
      });
    });
    setNodes(newNodes);
    const zoom = 0.7;
    setViewport({
      x: 0,
      y: 0,
      zoom,
    });
    saveStateToHistory(WorkflowHistoryEvent.LayoutOrganize);
    setTimeout(() => {
      handleSyncWorkflowDraft();
    });
  }, [getNodesReadOnly, store, reactflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory]);
  return {
    handleLayout,
  };
};

export const useWorkflowZoom = () => {
  const { handleSyncWorkflowDraft } = useNodesSyncDraft();
  const { getWorkflowReadOnly } = useWorkflowReadOnly();
  const { zoomIn, zoomOut, zoomTo, fitView } = useReactFlow();

  const handleFitView = useCallback(() => {
    if (getWorkflowReadOnly()) return;

    fitView();
    handleSyncWorkflowDraft();
  }, [getWorkflowReadOnly, fitView, handleSyncWorkflowDraft]);

  const handleBackToOriginalSize = useCallback(() => {
    if (getWorkflowReadOnly()) return;

    zoomTo(1);
    handleSyncWorkflowDraft();
  }, [getWorkflowReadOnly, zoomTo, handleSyncWorkflowDraft]);

  const handleSizeToHalf = useCallback(() => {
    if (getWorkflowReadOnly()) return;

    zoomTo(0.5);
    handleSyncWorkflowDraft();
  }, [getWorkflowReadOnly, zoomTo, handleSyncWorkflowDraft]);

  const handleZoomOut = useCallback(() => {
    if (getWorkflowReadOnly()) return;

    zoomOut();
    handleSyncWorkflowDraft();
  }, [getWorkflowReadOnly, zoomOut, handleSyncWorkflowDraft]);

  const handleZoomIn = useCallback(() => {
    if (getWorkflowReadOnly()) return;

    zoomIn();
    handleSyncWorkflowDraft();
  }, [getWorkflowReadOnly, zoomIn, handleSyncWorkflowDraft]);

  return {
    handleFitView,
    handleBackToOriginalSize,
    handleSizeToHalf,
    handleZoomOut,
    handleZoomIn,
  };
};

export const useWorkflowUpdate = () => {
  const reactflow = useReactFlow();
  const workflowStore = useWorkflowStore();
  const { eventEmitter } = useEventEmitterContextContext();

  const handleUpdateWorkflowCanvas = useCallback(
    (payload: WorkflowDataUpdater) => {
      const { nodes, edges, viewport } = payload;
      const { setViewport } = reactflow;
      eventEmitter?.emit({
        type: WORKFLOW_DATA_UPDATE,
        payload: {
          nodes: initialNodes(nodes, edges),
          edges: initialEdges(edges, nodes),
        },
      } as any);
      setViewport(viewport);
    },
    [eventEmitter, reactflow],
  );

  const handleRefreshWorkflowDraft = useCallback(() => {
    const { appId, setSyncWorkflowDraftHash, setIsSyncingWorkflowDraft, setEnvironmentVariables, setEnvSecrets, setConversationVariables } =
      workflowStore.getState();
    setIsSyncingWorkflowDraft(true);
    fetchWorkflowDraft(`/workflows/draft?id=${appId}`)
      .then((response) => {
        handleUpdateWorkflowCanvas(response.graph as WorkflowDataUpdater);
        setSyncWorkflowDraftHash(response.hash);
        setEnvSecrets(
          (response.environment_variables || [])
            .filter((env) => env.value_type === 'secret')
            .reduce(
              (acc, env) => {
                acc[env.id] = env.value;
                return acc;
              },
              {} as Record<string, string>,
            ),
        );
        setEnvironmentVariables(
          response.environment_variables?.map((env) => (env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env)) || [],
        );
        // #TODO chatVar sync#
        setConversationVariables(response.conversation_variables || []);
      })
      .finally(() => setIsSyncingWorkflowDraft(false));
  }, [handleUpdateWorkflowCanvas, workflowStore]);

  return {
    handleUpdateWorkflowCanvas,
    handleRefreshWorkflowDraft,
  };
};

export const useDSL = () => {
  const { t } = useTranslation();
  const { notify } = useToastContext();
  const { eventEmitter } = useEventEmitterContextContext();
  const [exporting, setExporting] = useState(false);
  const { doSyncWorkflowDraft } = useNodesSyncDraft();

  const appDetail = useAppStore((s) => s.appDetail);

  const handleExportDSL = useCallback(
    async (include = false) => {
      if (!appDetail) return;

      if (exporting) return;

      try {
        setExporting(true);
        await doSyncWorkflowDraft();
        const { data } = await exportAppConfig({
          appID: appDetail.id,
          include,
        });
        const a = document.createElement('a');
        const file = new Blob([data], { type: 'application/yaml' });
        a.href = URL.createObjectURL(file);
        a.download = `${appDetail.name}.yml`;
        a.click();
      } catch (e) {
        notify({ type: 'error', message: t('app.exportFailed') });
      } finally {
        setExporting(false);
      }
    },
    [appDetail, notify, t, doSyncWorkflowDraft, exporting],
  );

  const exportCheck = useCallback(async () => {
    if (!appDetail) return;
    try {
      const workflowDraft = await fetchWorkflowDraft(`/workflows/draft?id=${appDetail?.id}`);
      const list = (workflowDraft.environment_variables || []).filter((env) => env.value_type === 'secret');
      if (list.length === 0) {
        handleExportDSL();
        return;
      }
      eventEmitter?.emit({
        type: DSL_EXPORT_CHECK,
        payload: {
          data: list,
        },
      } as any);
    } catch (e) {
      notify({ type: 'error', message: t('app.exportFailed') });
    }
  }, [appDetail, eventEmitter, handleExportDSL, notify, t]);

  return {
    exportCheck,
    handleExportDSL,
  };
};
